package Server.players;

import java.util.Random;

import Server.map.Box;

/*
 * @public invariant this.getId() > 0 &&
 * this.getEnergy() > 0 && this.getDimension() > 0 &&
 * this.getAge() > 0 && this.getAgeLimit() > 0
 */
public abstract class Dino implements Cloneable{
	
	protected Integer idDino;
	protected Integer energy;
	protected Integer dimension;
	protected Integer age;
	protected Integer ageLimit;
	protected Box position;
	
	public static final int ENERGY_REQUIRED_EGG = 1500;
	
	// @requires id > 0
	public Dino(int id){
		this.idDino = id;
		this.energy = 1000;
		this.dimension = 1;
		this.age = 0;
		this.ageLimit = this.getRandomAgeLimit();
	}
	
	// @requires d != null
	public Dino(Dino d){
		this.idDino = d.idDino;
		this.energy = d.energy;
		this.dimension = d.dimension;
		this.age = d.age;
		this.ageLimit = d.ageLimit;
	}
	
	/* @requires id > 0 && location != null
	 * @signals (IllegalArgumentException e) location.isWater() 
	 */
	public Dino(int id, Box location){
		this.idDino = id;
		this.energy = 1000;
		this.dimension = 1;
		this.age = 0;
		this.ageLimit = this.getRandomAgeLimit();
		
		if (location.isWater())
			throw new IllegalArgumentException();
		this.position = new Box(location.getX(), location.getY(), false);
	}
	
	// @ensures \result > 24 && \result < 36
	private int getRandomAgeLimit(){
		Random r = new Random();
		return r.nextInt(12) + 24;
	}
	
	public int getX() {
		return this.position.getX();
	}
	public int getY() {
		return this.position.getY();
	}
	public Integer getEnergy() {
		return this.energy;
	}
	public Integer getDimension() {
		return this.dimension;
	}
	public Integer getAge() {
		return this.age;
	}
	public Integer getAgeLimit() {
		return this.ageLimit;
	}
	public Integer getId() {
		return this.idDino;
	}
	
	/* @ensures (this.getDimension() == 1 <==> (\result == 2)) &&
	 * ((this.getDimension() == 2 || this.getDimension() == 3) <==> (\result == 3)) &&
	 * ((this.getDimension() == 4 || this.getDimension() == 5) <==> (\result == 4))
	 */
	public int view() {
		if (this.dimension == 1)
			return 2;
		else if (this.dimension < 4){
			return 3;
		}
		else {
			return 4;
		}
	}
	

	// @ensures ((\result == true) <==> (b == this)
	@Override
	public abstract boolean equals(Object b);
	
	@Override
	public abstract int hashCode();
	@Override
	public Object clone() throws CloneNotSupportedException {
		Dino clone = (Dino)super.clone();
		
		if (this.position != null)
			clone.position = new Box(this.position);
		return clone;
	}
	
	// @ensures \result == true <==> this instanceof Carnivorous 
	public abstract boolean isCarniv();
	// @ensures \result == true <==> this instanceof Herbivorous
	public abstract boolean isHerb();
	public abstract int maxDistance();

	/*
	 * @requires foodBox != null
	 * @ensures this.getEnergy() >= \old(this.getEnergy()
	 */
	public abstract void eat(Box foodBox);
	
	/* 
	 * @requires d != null
	 * @ensures \result == this || \result == d
	 */
	public abstract Dino fight(Dino d);

	/* 
	 * @requires b != null
	 * @ensures this.getX() == b.getX() && this.getY() == b.getY()
	 */
	public void place(Box b){
		this.position = new Box(b);
	}
	
	/*
	 * @ensures this.getAge() >= \old(this.getAge()) &&
	 * (\result == true) <==> (this.getAge() < this.getAgeLimit)
	 */
	public boolean older(){
		if (this.age < this.ageLimit)
		{
			this.age++;
			return true;
		}
		return false;
	}

	/*
	 * @ensures this.getEnergy() < \old(this.getEnergy()) &&
	 * (\result == false) <==> (this.Energy() < 0 )
	 */
	public boolean egg() {
		this.energy -= Dino.ENERGY_REQUIRED_EGG;
		if (this.energy < 0)
			return false;
		return true;
	}

	/*
	 * @ensures this.getEnergy() < \old(this.getEnergy()) &&
	 * (\result == false) <==> (this.Energy() < 0 )
	 */
	public boolean grow() {
		if (this.dimension > 4)
			return true;
		
		this.energy -= (this.dimension * 1000) / 2;
		if (this.energy < 0){
			return false;
		}
		
		this.dimension++;
		return true;
	}

	/*
	 * @ensures (\result == true) <==> (this.Energy() >= Math.pow(2, distance) )
	 */
	public boolean enoughEnergyToMove(int distance){
		if (this.energy >= 10 * (int)(Math.pow(2, distance)))
			return true;
		return false;
	}
	
	/*
	 * @requires destination != null
	 * @ensures  this.getEnergy() < \old(this.getEnergy()) &&
	 * (\result == false) <==> (this.Energy() < 0 ) &&
	 * this.getX() == b.getX() && this.getY() == b.getY()
	 * @signals (IllegalArgumentException e) location.isWater() 
	 */
	public boolean move(Box destination, int distance) {
		if (destination.isWater())
			throw new IllegalArgumentException();
		
		this.energy -= 10 * (int)(Math.pow(2, distance));
		if (this.energy < 0){
			return false;
		}
		
		this.position = new Box(destination);
		return true;
	}
}
